package com.sinog.front.util;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.TemplateExportParams;
import com.sinog.core.utilbean.Constants;
import com.sinog.core.utilbean.IdGen;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @desc 文件上传下载
 * @author hak
 * @date 2019年11月26日
 */
@Slf4j
@Component
public final class FileUtil {

    private static String uploadPath;
    private static String linuxUploadPath;
    private static String uploadFilePath;
    private static String linuxUploadFilePath;

    @Value("${gkzx.azbj.fileTemplate.windows}")
    public void setUploadPath(String uploadPath) {
        FileUtil.uploadPath = uploadPath;
    }

    @Value("${gkzx.azbj.fileTemplate.unix}")
    public void setLinuxUploadPath(String linuxUploadPath) {
        FileUtil.linuxUploadPath = linuxUploadPath;
    }

    @Value("${gkzx.azbj.uploadfilepath.windows}")
    public void setUploadFilePath(String uploadFilePath) {
        FileUtil.uploadFilePath = uploadFilePath;
    }

    @Value("${gkzx.azbj.uploadfilepath.unix}")
    public void setLinuxUploadFilePath(String linuxUploadFilePath) {
        FileUtil.linuxUploadFilePath = linuxUploadFilePath;
    }

    /**
     * 获取文件上传路径
     * @return String
     */
    public static String getUploadFilePath() {
        String path = uploadFilePath;
        String os = System.getProperty("os.name");
        if(!os.toLowerCase().startsWith(Constants.StrFinalValue.WIN)) {
            path = linuxUploadFilePath;
        }
        return path;
    }

    /**
     * 获取文件模版路径
     * @return String
     */
    public static String getFileTemplatePath() {
        String path = uploadPath;
        String os = System.getProperty("os.name");
        if(!os.toLowerCase().startsWith(Constants.StrFinalValue.WIN)) {
            path = linuxUploadPath;
        }
        return path;
    }

    /**
     * 下载单个文件
     * @param request request
     * @param resp resp
     * @param filePath filePath
     * @param filename filename
     */
    public static void downloadFile(HttpServletRequest request,HttpServletResponse resp,String filePath,String filename) throws IOException {
        log.info("下载单个文件路径:filePath:{}",filePath);
        //获取浏览器
        String userAgent = request.getHeader("User-Agent");
        if(userAgent.contains(Constants.StrFinalValue.MSIE) || userAgent.contains(Constants.StrFinalValue.TRIDENT)) {
            //IE浏览器处理
            filename = URLEncoder.encode(filename,"UTF-8");
        } else {
            // 非IE浏览器的处理
            filename = new String(filename.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1);
        }
        File file = new File(filePath);
        //判断文件是否存在
        if(file.exists()) {
            resp.setContentType("application/force-download");
            resp.setHeader("Content-Disposition","attachment;filename=" + filename);
            resp.setContentLength((int)file.length());
            byte[] buffer = new byte[1024];
            //文件输入流
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            try {
                fis = new FileInputStream(file);
                bis = new BufferedInputStream(fis);
                OutputStream os = resp.getOutputStream();
                int len;
                while(-1 != (len = bis.read(buffer))) {
                    os.write(buffer,0,len);
                }
                resp.flushBuffer();
            } catch(FileNotFoundException e) {
                log.error("文件未找到:",e);
            } catch(IOException e) {
                log.error("发生异常:",e);
            } finally {
                try {
                    if(null != bis) {
                        bis.close();
                    }
                } catch(IOException e) {
                    log.error("发生异常:",e);
                }
                try {
                    if(null != fis) {
                        fis.close();
                    }
                } catch(IOException e) {
                    log.error("发生异常:",e);
                }
            }
        }
    }

    /**
     * 信息导出 注解模板
     * @param request request
     * @param map map
     * @param excelName excelName
     * @param templateUrl templateUrl
     * @param response response
     */
    public static void exportExcelFile(HttpServletRequest request,Map<String,Object> map,String excelName,String templateUrl,HttpServletResponse response) throws IOException {
        //获取浏览器
        String userAgent = request.getHeader("User-Agent");
        if(userAgent.contains(Constants.StrFinalValue.MSIE) || userAgent.contains(Constants.StrFinalValue.TRIDENT)) {
            //IE浏览器处理
            excelName = URLEncoder.encode(excelName,"UTF-8");
        } else {
            // 非IE浏览器的处理
            excelName = new String(excelName.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1);
        }
        // 加载模板
        TemplateExportParams params = new TemplateExportParams(templateUrl);
        //输出流
        OutputStream os = null;
        try {
            // 生成workbook 并导出
            Workbook workbook = ExcelExportUtil.exportExcel(params,map);
            response.addHeader("Content-Disposition","attachment;filename=" + excelName);
            os = new BufferedOutputStream(response.getOutputStream());
            workbook.write(os);
        } catch(Exception e) {
            log.error("发生异常:",e);
        } finally {
            try {
                if(null != os) {
                    os.close();
                }
            } catch(Exception e) {
                log.error("发生异常:",e);
            }
        }
    }

    /**
     * 删除单个文件
     * @param path 被删除文件的文件名
     * @return 单个文件删除成功返回true，否则返回false
     */
    public static boolean deleteFile(String path) {
        boolean flag = false;
        File file = new File(path);
        // 路径为文件且不为空则进行删除
        if(file.isFile() && file.exists()) {
            try {
                file.delete();
            } catch(RuntimeException e) {
                log.error(e.getMessage());
            }
            flag = true;
        }
        return flag;
    }

    /**
     * 递归压缩方法
     * @param sourceFile 源文件
     * @param zos zip输出流
     * @param name 压缩后的名称
     * @param keepDirStructure 是否保留原来的目录结构,true:保留目录结构; false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     */
    private static void compress(File sourceFile,ZipOutputStream zos,String name,boolean keepDirStructure) throws Exception {
        byte[] buf = new byte[1024];
        if(sourceFile.isFile()) {
            // 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字
            zos.putNextEntry(new ZipEntry(name));
            // copy文件到zip输出流中
            int len;
            try(FileInputStream in = new FileInputStream(sourceFile)) {
                while((len = in.read(buf)) != -1) {
                    zos.write(buf,0,len);
                }
                zos.closeEntry();
            } catch(Exception e) {
                log.error("发生异常:",e);
            }
        } else {
            //是文件夹
            File[] listFiles = sourceFile.listFiles();
            if(null == listFiles || 0 == listFiles.length) {
                // 需要保留原来的文件结构时,需要对空文件夹进行处理
                if(keepDirStructure) {
                    // 空文件夹的处理
                    zos.putNextEntry(new ZipEntry(name + "/"));
                    // 没有文件，不需要文件的copy
                    zos.closeEntry();
                }
            } else {
                for(File file : listFiles) {
                    // 判断是否需要保留原来的文件结构
                    if(keepDirStructure) {
                        // 注意：file.getName()前面需要带上父文件夹的名字加一斜杠,
                        // 不然最后压缩包中就不能保留原来的文件结构,即：所有文件都跑到压缩包根目录下了
                        compress(file,zos,name + "/" + file.getName(),keepDirStructure);
                    } else {
                        compress(file,zos,file.getName(),keepDirStructure);
                    }
                }
            }
        }
    }

    /**
     * 压缩成ZIP 方法
     * @param srcDir 压缩文件夹路径
     * @param out 压缩文件输出流
     * @param keepDirStructure 是否保留原来的目录结构,true:保留目录结构; false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     */
    public static void toZip(String srcDir,OutputStream out,boolean keepDirStructure) throws RuntimeException {
        ZipOutputStream zos = null;
        try {
            zos = new ZipOutputStream(out);
            File sourceFile = new File(srcDir);
            compress(sourceFile,zos,sourceFile.getName(),keepDirStructure);
        } catch(Exception e) {
            throw new RuntimeException("zip error from FileUtil",e);
        } finally {
            if(null != zos) {
                try {
                    zos.close();
                } catch(IOException e) {
                    log.error("发生异常:",e);
                }
            }
        }
    }

    /**
     * 复制单个文件
     * @param oldPath String 原文件路径 如：c:/fqf.txt
     * @param newPath String 复制后路径 如：f:/fqf.txt
     * @param fileName String 新文件名
     */
    public static void copyFile(String oldPath,String newPath,String fileName) {
        File newFile = new File(newPath);
        if(!newFile.exists()) {
            try {
                newFile.mkdirs();
            } catch(RuntimeException e) {
                log.error(e.getMessage());
            }
        }
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            int byteread;
            File oldfile = new File(oldPath);
            //文件存在时
            if(oldfile.exists()) {
                //读入原文件
                fis = new FileInputStream(oldPath);
                fos = new FileOutputStream(newPath + File.separator + fileName);
                byte[] buffer = new byte[1444];
                while((byteread = fis.read(buffer)) != -1) {
                    fos.write(buffer,0,byteread);
                }
                fis.close();
            }
        } catch(Exception e) {
            log.error("复制单个文件操作出错:",e);
        } finally {
            if(null != fis) {
                try {
                    fis.close();
                } catch(IOException e) {
                    log.error("发生异常:",e);
                }
            }
            if(null != fos) {
                try {
                    fos.close();
                } catch(IOException e) {
                    log.error("发生异常:",e);
                }
            }
        }
    }

    /**
     * 迭代删除文件夹
     * @param file 文件夹
     */
    public static void deleteDir(File file) {
        if(file.isFile() && file.exists()) {
            try {
                file.delete();
            } catch(RuntimeException e) {
                log.error(e.getMessage());
            }
        } else {
            File[] files = file.listFiles();
            for(File f : files) {
                try {
                    deleteDir(f);
                } catch(RuntimeException e) {
                    log.error(e.getMessage());
                }
            }
            try {
                file.delete();
            } catch(RuntimeException e) {
                log.error(e.getMessage());
            }
        }
    }

    /**
     * 根据文件对象获取文件名和文件路径
     * @param file file
     * @return Map<String,String>
     */
    public static Map<String,String> transfer(MultipartFile file) {
        Map<String,String> map = new HashMap<>(16);
        String fileName = file.getOriginalFilename();
        fileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1);
        String id = IdGen.strid();
        String tempFileName = id + fileName.substring(fileName.lastIndexOf('.'));
        String path = getFileTemplatePath();
        String filePath = path + tempFileName;
        File desFile = new File(filePath);
        if(!desFile.getParentFile().exists()) {
            try {
                desFile.getParentFile().mkdirs();
            } catch(RuntimeException e) {
                log.error(e.getMessage());
            }
        }
        try {
            file.transferTo(desFile);
        } catch(IOException e) {
            log.error("发生异常:",e);
        }
        map.put("fileName",fileName);
        map.put("filePath",tempFileName);
        return map;
    }

    /**
     * 描述
     * @param map 数据
     * @param response response
     * @param templatePath 模板父路径
     * @param templateFileName 模板文件名
     * @param downFileName 下载文件名
     */
    public static void exportDataDoc(Map<String,Object> map,HttpServletResponse response,String templatePath,String templateFileName,String downFileName) throws IOException {
        //必须这样写，构造中指定版本号，不然会被画删除线
        Configuration config = new Configuration(Configuration.getVersion());
        //为了避免错误，直接指定字符集，下文也有需要指定的地方
        config.setDefaultEncoding("UTF-8");
        Template temp;
        response.addHeader("Content-Disposition","attachment;filename=" + new String(downFileName
                // filename指定默认的名字
                .getBytes(),"iso8859-1"));
        response.setCharacterEncoding("UTF-8");
        ServletOutputStream outStr = null;
        OutputStreamWriter owt = null;
        Writer out = null;
        try {
            // 建立
            outStr = response.getOutputStream();
            owt = new OutputStreamWriter(outStr,StandardCharsets.UTF_8);
            out = new BufferedWriter(owt);
            //设置模板路径
            config.setDirectoryForTemplateLoading(new File(templatePath));
            temp = config.getTemplate(templateFileName,"UTF-8");
            temp.process(map,out);
        } catch(Exception e) {
            log.error("发生异常:",e);
        } finally {
            try {
                Objects.requireNonNull(owt).close();
            } catch(Exception e) {
                log.error("发生异常:",e);
            }
            try {
                Objects.requireNonNull(out).close();
            } catch(Exception e) {
                log.error("发生异常:",e);
            }
            try {
                Objects.requireNonNull(outStr).close();
            } catch(Exception e) {
                log.error("发生异常:",e);
            }
        }
    }
}